/*
 * Copyright (C) 2010 by Lei Shi (sjtushi@gmail.com)
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License, 
 * version 2, or any later versions.
 * --------------------
 * This program is build on XenAccess library which is created Brayan Payne. 
 *     http://code.google.com/p/xenaccess/
 *
 * This program could be used in Xen domain0 to list all opened files 
 * information of specfic process <pid> in paravirtualization guest 
 * domainU <domid>. 
 *
 * Usage: xen-lsof <domid> <pid>
 *
 * Like lsof command used in domainU linux.
 *
 */

#include "xen-lsof.h"

/* Get mount point from vfsmount*/
char *get_filemnt(xa_instance_t *xai, uint32_t vfsmnt_addr) {
   char* ret = NULL; 
   unsigned char* memory = NULL;
   char* root = NULL;
   char* mnt = NULL;
   uint32_t offset, vfsmount_root_addr, vfsmount_mntpoint_addr;

   memory = 
      xa_access_kernel_va(xai, vfsmnt_addr, &offset, PROT_READ);
   memcpy(&vfsmount_root_addr, 	       
	 memory + offset + VFSMOUNT_ROOT_OFFSET, 4);
   root = get_dentry_name(xai, vfsmount_root_addr);
   memcpy(&vfsmount_mntpoint_addr, 
	 memory + offset + VFSMOUNT_MNTPOINT_OFFSET, 4);
   mnt = get_dentry_name(xai, vfsmount_mntpoint_addr);
   munmap(memory,xai->page_size);

   ret =(char*)malloc(MAX_FILENAME_LENGTH);
   memset(ret, 0, MAX_FILENAME_LENGTH);
   if (strcmp(root,"socket:") && strcmp(root, "/"))
      strcat(ret, root);
   free(root);
   if (strcmp(mnt,"socket:") && strcmp(mnt, "/"))
      strcat(ret, mnt);
   free(mnt);

   return ret;
}

void free_list(proc_file_info_t* list) {
   proc_file_info_t* ptr = list;
   proc_file_info_t* next = NULL;
   while(ptr != NULL) {
      next = ptr->next;
      free(ptr);
      ptr = next;
   }
}

void print_file_info(proc_file_info_t *list) {
   proc_file_info_t *ptr = list;
   char flagstr[20];
   printf("FD\tFileName\t\tFFLAGS\n");

   while(ptr != NULL) {
      switch (ptr->flag) {
	 case 0: sprintf(flagstr, "0(read only)"); break;
	 case 1: sprintf(flagstr, "1(write only)"); break;
	 case 2: sprintf(flagstr, "2(read & write)"); break;
	 default: sprintf(flagstr, "%d(Unknown mode)", ptr->flag); break;
      }
      printf("%d\t%s\t\t%s\n", ptr->fd, ptr->filename, flagstr);
      ptr = ptr->next;
   }
}

/* get dentry name string with dentry virtual address */
char* get_dentry_name(xa_instance_t* xai, addr_t dentry_addr) {
   unsigned char* memory = NULL;
   char* retname = NULL;
   char* name = NULL;
   uint32_t offset, dname_name_addr, parent_addr, temp_name_addr;

   stack_t stack;
   init_stack(&stack);

   /* find struct qstr d_name.name in dentry */
   /* find char *name address in qstr dname */
   memory = 
      xa_access_kernel_va(xai, dentry_addr, &offset, PROT_READ);

   if (memory == NULL) {
      printf("Memory map failed\n");
      return NULL;
   }

   memcpy(&dname_name_addr, 
	 memory + offset + DENTRY_DNAME_OFFSET + QSTR_NAME_OFFSET, 4);
   memcpy(&parent_addr, 
	 memory + offset + DENTRY_PARENT_OFFSET, 4);
   munmap(memory, xai->page_size);

   retname = (char *)malloc(MAX_FILENAME_LENGTH);
   memset(retname, 0, MAX_FILENAME_LENGTH);

   while (parent_addr != 0) {
      memory = 
	 xa_access_kernel_va(xai, parent_addr, &offset, PROT_READ);
      memcpy(&temp_name_addr, 
   	    memory + offset + DENTRY_DNAME_OFFSET + QSTR_NAME_OFFSET, 4);
      memcpy(&parent_addr, 
   	    memory + offset + DENTRY_PARENT_OFFSET, 4);
      munmap(memory, xai->page_size);

      memory = 
	 xa_access_kernel_va(xai, temp_name_addr, &offset, PROT_READ);

      name = (char *) (memory + offset);
      if (!strcmp(name, "socket:")) {
	 strcat(retname, name);
	 break;
      }

      if (!strcmp(name, "/")) {
	 while (!isempty_stack(&stack)) {
	    strcat(retname, pop_stack(&stack));
	    strcat(retname, "/");
	 }
	 break;
      }
      // now we only handle 10 levels directory
      push_stack(&stack, name);
      munmap(memory, xai->page_size);
   }
   if (memory != NULL) 
      munmap(memory, xai->page_size);

   memory = xa_access_kernel_va(xai, dname_name_addr,&offset, PROT_READ);

   name = (char *) (memory + offset);
   if (strcmp(name, "socket:") && strcmp(name, "/")) 
      strcat(retname, name);
   munmap(memory, xai->page_size);

   return retname;
}



proc_file_info_t* get_files_info(xa_instance_t* xai, addr_t files_vaddr) {
   unsigned char *files_memory = NULL;
   unsigned char *memory = NULL;
   proc_file_info_t* list = NULL;
   proc_file_info_t* cur = NULL;
   proc_file_info_t* pre = NULL;
   uint32_t offset, files_offset;
   uint32_t max_fd = 0;
   uint32_t fd_addr, file_mem_addr, fdarray_fentry_addr, 
	    fdarray_fvfsmount_addr;
   uint32_t fd = 0;
   uint32_t fflags;
   char *name = NULL;
   char *mnt = NULL;

   /* Map task_struct->files memory */
   files_memory = 
      xa_access_kernel_va(xai, files_vaddr, &files_offset, PROT_READ);

   if (files_memory == NULL) {
      return NULL;
   }

   memcpy(&max_fd, 
	 files_memory + files_offset + FILESTRUCT_FDTAB_OFFSET, 4);

   memcpy(&fd_addr, 
	 files_memory + files_offset + 
	 FILESTRUCT_FDTAB_OFFSET + FDTAB_FD_OFFSET, 4);

   munmap(files_memory, xai->page_size);

   /* map fd_array array memory to domain0 */
   files_memory = 
      xa_access_kernel_va(xai, fd_addr, &files_offset, PROT_READ);

   if (files_memory == NULL) {
      return NULL;
   }

   /* walk through the fd_array */
   for (fd = 0; fd < max_fd; fd++) {
      memcpy(&file_mem_addr, files_memory + files_offset + fd*4, 4);

      if (file_mem_addr == 0) {
	 continue;
      }

      /* find struct dentry* f_entry in struct file */
      memory = 
	 xa_access_kernel_va(xai, file_mem_addr, &offset, PROT_READ);

      if (memory == NULL) {
	 printf("Memory map failed\n");
	 break;
      }

      memcpy(&fdarray_fentry_addr, 
	    memory + offset + FILE_FDENTRY_OFFSET, 4);
      memcpy(&fdarray_fvfsmount_addr, 
	    memory + offset + FILE_FVFSMOUNT_OFFSET, 4);
      memcpy(&fflags, 
	       memory + offset + FILE_FFLAGS_OFFSET, 4);
      munmap(memory, xai->page_size);


      name = get_dentry_name(xai, fdarray_fentry_addr);
      mnt = get_filemnt(xai, fdarray_fvfsmount_addr);

      cur = (proc_file_info_t*)malloc(sizeof(proc_file_info_t));
      cur->fd = fd;
      cur->flag = fflags;
      memset(cur->filename, 0, MAX_FILENAME_LENGTH);

      if (strstr(name, "socket:[") == NULL)
   	 strcat(cur->filename, "/");
      strcat(cur->filename, mnt);
      if (strlen(mnt) > 0)
	 strcat(cur->filename, "/");
      free(mnt);
      strcat(cur->filename, name);
      free(name);
      cur->next = NULL;

      if (list == NULL) {
	 list = cur;
	 pre = list;
      } else {
	 pre->next = cur;
	 pre = cur;
      }
   }

   munmap(files_memory, xai->page_size);

   return list;
}

int main(int argc, char **argv) {
   xa_instance_t xai;
   unsigned char *memory = NULL;
   proc_file_info_t *list = NULL;
   uint32_t offset, task_addr, list_head;
   uint32_t domid = 0;

   proc_t proc;
   uint32_t files_vaddr = 0;

   if (argc != 3) {
      printf("Usage: xen-lsof <domid> <pid>\n");
      printf("       xen-lsof will list the opened file list of this pid process in domain.\n");
      return STATUS_ERROR;
   }

   domid = atoi(argv[1]);
   proc.domid = domid;

   if (init_xa_instance(&xai, domid) != STATUS_SUCCESS){
      return STATUS_ERROR;
   }

   uint32_t pid = atoi(argv[2]);

   memory = 
      xa_access_kernel_sym(&xai, "init_task", &offset, PROT_READ);
   CHECK_MAPPED_MEMORY(memory);
   memcpy(&list_head, 
	 memory + offset + TASKSTRUCT_TASKS_OFFSET, 4);

   task_addr = list_head;
   munmap(memory, xai.page_size);

   while(1) {
      memory = 
	 xa_access_kernel_va(&xai, task_addr, &offset, PROT_READ);
      CHECK_MAPPED_MEMORY(memory);
      memcpy(&task_addr, memory + offset, 4);

      if (task_addr == list_head) {
	 printf("Cannot find process with pid %d in domain\n", pid);
	 break;
      }

      if (getproc_from_guest((addr_t)(memory + offset), &proc) 
	    != STATUS_SUCCESS) {
   	 munmap(memory, xai.page_size);
      	 xa_destroy(&xai);
	 return STATUS_ERROR;
      }

      if (proc.pid == pid) {
      	 /*find struct files_struct *files in task_struct*/
      	 memcpy(&files_vaddr, 
	       memory + offset + TASKSTRUCT_FILES_OFFSET - 
	       TASKSTRUCT_TASKS_OFFSET, 4);
#ifdef DEBUG
      	 printf("struct files_struct address: %x\n", file_addr);
#endif
   	 munmap(memory, xai.page_size);
   	 break;
      }

      munmap(memory, xai.page_size);
   }

   if (task_addr == list_head) {
      xa_destroy(&xai);
      return STATUS_ERROR;
   }

   print_proc(&proc, PRINT_PROC_LSOF_MODE);
   list = get_files_info(&xai, files_vaddr);
   print_file_info(list);
   free_list(list);

   if (memory != NULL) 
      munmap(memory, xai.page_size);
   xa_destroy(&xai);
   return 0;
}

